package algorithms.concCBJ;

import general.TwoLongs;
import general.CPA.ArrayCPA;
import general.CPA.CPA;
import general.constraints.Constraints;

import java.util.Arrays;
import java.util.Collections;
import java.util.Vector;

import ordering.VariableOrdering;
import ordering.stat.DefaultOrdering;

import priorities.MiddleFirst;
import priorities.PriorityManager;
import problem.Problem;

/**
 *	An Agent dispatcher, responsible for creating, connecting and running the agents
 */
public class ConcCBJAgentMaker {
	byte numAgents;
	byte domain;
	ConcCBJAgent[] concCBJAgents;

	/**
	 * Creates an instance of ConcBTAgentMaker
	 * @param numAgents number of agents to create
	 * @param domain domain for each agent
	 * @param splitLimit number of steps before splitting
	 */
	public ConcCBJAgentMaker(byte numAgents, byte domain){
		this.numAgents = numAgents;
		this.domain = domain;
		this.concCBJAgents = new ConcCBJAgent[numAgents];
	}

	/**
	 * Creates the agents and connects them together
	 * First, creates each agent and starts them on listening mode (to listen for incoming connections from the other agents)
	 * (waits for the agents to be ready after each step)
	 * Second, tells all of the agents to connect to their friends, supplying the ports
	 * Third, for each agent that received all of the connections it needs and sent the rest - opens a selector
	 * Finally when all of the selectors are ready - this method returns
	 * @param constraints the constraints to assign to the agents
	 * @param ordering variable ordering heuristics to assign to the agents
	 * @param protoCpa a CPA prototype that the agents will use to create new CPAs
	 */
	public void createAgents(Constraints constraints, VariableOrdering ordering, CPA protoCpa, int delay, int splitLimit){
		int[] ports = new int[numAgents];
		//creates the agents and start them on listening mode
		for (byte i=0; i<numAgents; i++){
			concCBJAgents[i] = new ConcCBJAgent(i, numAgents, domain, splitLimit, constraints.getVariableConstraints(i), ordering, protoCpa, delay); 	//TODO
			ports[i] = concCBJAgents[i].startListening();
			concCBJAgents[i].getConnections();
		}

		//waits for all agents to start listening
		for (int i=0; i<numAgents; i++){
			while (!concCBJAgents[i].isReadyForConnections()) myWait(250);
		}

		//creates the connections between the agents
		for (int i=0; i<numAgents; i++){
			concCBJAgents[i].createConnections(Arrays.copyOfRange(ports, 0, i));
		}

		//creates a reactor for each of the ready agents
		for (int i=0; i<numAgents; i++){
			while (!concCBJAgents[i].isReadyForSelector()) myWait(250);
			concCBJAgents[i].printStatus();
			concCBJAgents[i].openSelector();
		}

		//waits for all of the agents to have their reactor ready
		for (int i=0; i<numAgents; i++){
			while (!concCBJAgents[i].isReadySelector()) myWait(250);
		}
//		System.out.println("All agents ready");
	}

	/**
	 * delays for a given amount of time
	 * @param milis milliseconds to wait
	 */
	private void myWait(long milis) {
//		System.out.println("Waiting " + milis + " miliseconds");
		try {
			String m = "";
			synchronized (m) {
				m.wait(milis);
			}
		} catch (InterruptedException e) {
			e.printStackTrace();
		}
	}

	/**
	 * Starts all of the agents and waits for all of them to finish
	 */
	private void startAllAgents(){
		
		//starts the agnets
		for (int i=0; i<concCBJAgents.length; i++){
			concCBJAgents[i].start();
		}

		try {
			//waits for the agents to return
			for (int i=0; i<concCBJAgents.length; i++){
				concCBJAgents[i].join();
			}
		} catch (InterruptedException e) {
			e.printStackTrace();
		}
//		System.out.println("All threads are dead :)");

		for (int i=0; i<concCBJAgents.length; i++){
			//concBTAgents[i].killTimer();
			concCBJAgents[i].close();
		}
	}

	/**
	 * Solves a problem and returns the total number of NCCCs and total number of messages sent
	 * (NCCCs is the first one in the TwoLongs object, number of messages is the second) 
	 * @param c constraints for the agents
	 * @param ordering variable ordering heuristics for the agents
	 * @param protoCpa prototype CPA for using with the agents
	 * @param delay - delay in milliseconds of the messages
	 * @return the total number of NCCCs and the total number of messages sent
	 */
	public TwoLongs solve(Constraints c, VariableOrdering ordering, CPA protoCpa, int delay, PriorityManager manager, int splitLimit){
		PriorityManager.reset();
		PriorityManager.init(manager);
		createAgents(c, ordering, protoCpa, delay, splitLimit);
		startAllAgents();
		long NCCCs = 0;
		long messages = 0;
		for (int i=0; i<concCBJAgents.length; i++){
			NCCCs = NCCCs + concCBJAgents[i].getNCCs();
			messages = messages + concCBJAgents[i].getNumMessages();
		}
//		System.out.println(NCCCs + " " + messages);
		return new TwoLongs(NCCCs, messages);
	}
	
	/**
	 * 
	 * @return the solution to this problem
	 */
	public String getSolution(){
		return concCBJAgents[0].getSolution();
	}

	public static void main(String[] args) {
		int splitLimit = 15;
		byte n = 15;
		byte domain = n;
		ConcCBJAgentMaker m = new ConcCBJAgentMaker(n, domain);
		Constraints constraints = Problem.nQueensConstraints(n);
		//		m.solve(constraints, new ConstraintOrdering(n, constraints), new ArrayCPA(), 0);
		//		m.solve(constraints, new RandomStaticOrdering(n), new ArrayCPA(), 0);
		m.solve(constraints, new DefaultOrdering(n), new ArrayCPA(), 0, new MiddleFirst(n), splitLimit);
		//		m.solve(constraints, new DefaultOrdering(n), new DynamicCPA(), 0);
	}

	/**
	 * 
	 * @return a string containing information about the totalSPs and averages.
	 */
	public String getConcInfo() {
		double averageSPs = 0 ;
		double numSPs = 0;
		double maxSPs = 0;
		double minMaxSPs = concCBJAgents[0].getMaxSPs();
		double maxMaxSPs = concCBJAgents[0].getMaxSPs();
		
		for (int i=0; i<numAgents; i++){
			averageSPs = averageSPs + concCBJAgents[i].getAverageSPs()/(double)numAgents;
			numSPs = numSPs + concCBJAgents[i].getNumSPs()/(double)numAgents;
			maxSPs = maxSPs + concCBJAgents[i].getMaxSPs()/(double)numAgents;
			if (minMaxSPs > concCBJAgents[i].getMaxSPs()){
				minMaxSPs = concCBJAgents[i].getMaxSPs();
			}
			if (maxMaxSPs < concCBJAgents[i].getMaxSPs()){
				maxMaxSPs = concCBJAgents[i].getMaxSPs();
			}
		}
		return averageSPs + "\t" + numSPs + "\t" + maxSPs + "\t" + maxMaxSPs + "\t" + minMaxSPs;
	}

	public String getDistNCCCs() {
		String ans = "";
//		long NCCCs = 0;
//		for (int i=0; i<concBTAgents.length; i++){
//			NCCCs = NCCCs + concBTAgents[i].getNCCs();
//		}
//		System.out.println(NCCCs + ", " + (double)NCCCs);
//		double ncccs2 = NCCCs;
//		Vector<Double> ncccsVec= new Vector<Double>();
		Vector<Long> ncccsVec= new Vector<Long>();
		for (int i=0; i<concCBJAgents.length; i++){
//			ncccsVec.add((concBTAgents[i].getNCCs()/ncccs2));
			ncccsVec.add(concCBJAgents[i].getNCCs());
		}
		Collections.sort(ncccsVec);
		for (int i=0; i<concCBJAgents.length; i++){
			ans = ans + ncccsVec.get(i) + "\t";
		}
//		System.out.println(ans);
		return ans;
	}
	
	public String getDistMsgs() {
		String ans = "";
//		long messages = 0;
//		for (int i=0; i<concBTAgents.length; i++){
//			messages = messages + concBTAgents[i].getNumMessages();
//		}
//		System.out.println(messages + ", " + (double)messages);
//		double messages2 = messages;
//		Vector<Double> messagesVec= new Vector<Double>();
		Vector<Long> messagesVec= new Vector<Long>();
		for (int i=0; i<concCBJAgents.length; i++){
//			messagesVec.add((concBTAgents[i].getNumMessages()/messages2));
			messagesVec.add(concCBJAgents[i].getNumMessages());
		}
		Collections.sort(messagesVec);
		for (int i=0; i<concCBJAgents.length; i++){
			ans = ans + messagesVec.get(i) + "\t";
		}
//		System.out.println(ans);
		return ans;
	}
	
	public double getConcurrency(){
		double averageConc = 0 ;
		
		for (int i=0; i<numAgents; i++){
//			System.out.println(concBTAgents[i].getConcurrency());
			averageConc = averageConc + concCBJAgents[i].getConcurrency()/(double)numAgents;
		}
		return averageConc;
	}
}
